1 /*
2 * Scope: a generic MVC framework.
3 * Copyright (c) 2000-2002, The Scope team
4 * All rights reserved.
5 *
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * Neither the name "Scope" nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 *
36 * $Id: Selector.java,v 1.10 2002/09/12 18:23:54 ludovicc Exp $
37 */
38 package org.scopemvc.core;
39
40
41 import java.util.StringTokenizer;
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.scopemvc.util.Debug;
45
46 /***
47 * <P>
48 *
49 * An identifier for model properties. Selectors are created by the factory
50 * methods {@link #fromString} and {@link #fromInt}. Properties can be
51 * identified by a String name (eg the "address" property of a Customer) or an
52 * integer index (eg element 1 of a List). </P> <P>
53 *
54 * Selectors can be assembled in a list to identify a property in a model
55 * contained within another model. For example, the <CODE>name</CODE> of the
56 * <CODE>pet</CODE> of a <CODE>Person</CODE>. This Selector would be created by
57 * <CODE>Selector.fromString("pet.name")</CODE> and applied to a Person model
58 * object. Similarly, the name of a Person's first pet could be identified using
59 * <CODE>Selector.fromString("pets.0.name")</CODE> assuming that Person contains
60 * a List or array of Pets that contain a name property. </P>
61 *
62 * @author <A HREF="mailto:smeyfroi@users.sourceforge.net">Steve Meyfroidt</A>
63 * @created 05 August 2002
64 * @version $Revision: 1.10 $ $Date: 2002/09/12 18:23:54 $
65 */
66 public abstract class Selector {
67
68 /***
69 * Separator character between Selectors expressed as a String.
70 *
71 * @see #fromString
72 * @see #asString
73 */
74 public static final String DELIMITER = ".";
75
76 private static final Log LOG = LogFactory.getLog(Selector.class);
77
78 /***
79 * Link to next Selector in the list.
80 */
81 private Selector next;
82
83
84 /***
85 * Package private ctor for the factory. Application code should use {@link
86 * Selector#fromString} or {@link Selector#fromInt} to create Selectors.
87 */
88 Selector() { }
89
90
91 // -------------------- Factory -------------------------
92
93 /***
94 * Make a simple Selector to identify a property at an int index. eg this
95 * returns a Selector that identifies the first element of a List: <PRE>
96 * return Selector.fromInt(0);
97 * </PRE>
98 *
99 * @param inIndex The index of the property in a List
100 * @return A selector identifying a property in a List
101 */
102 public static IntIndexSelector fromInt(int inIndex) {
103 return new IntIndexSelector(inIndex);
104 }
105
106
107 /***
108 * Make a Selector to identify a property by its String name, or to create a
109 * Selector list that identifies a property by navigating a hierarchy of
110 * submodels. For example:
111 * <UL>
112 * <LI> <CODE>Selector.fromString("name");</CODE> will identify the name
113 * property of a Person when applied to a Person model object. </LI>
114 * <CODE>Selector.fromString("pet.0.name");</CODE> will identify the name
115 * property of the first Pet of a Person when applied to a Person model
116 * object.
117 * </UL>
118 *
119 *
120 * @param inSelectorDescription The string representation of a Selector
121 * @return A selector identifying a property
122 */
123 public static Selector fromString(String inSelectorDescription) {
124 if (LOG.isDebugEnabled()) {
125 LOG.debug("fromString: " + inSelectorDescription);
126 }
127
128 StringTokenizer tokenizer = new StringTokenizer(inSelectorDescription, DELIMITER);
129 Selector result = null;
130
131 while (tokenizer.hasMoreTokens()) {
132 String nextToken = tokenizer.nextToken();
133 // Empty property descriptor is a null Selector
134 if (nextToken.length() < 1) {
135 continue;
136 }
137
138 Selector nextSelector = null;
139 // OK then, does it parse as an int so we get an IntIndexSelector?
140 // TODO: this is a bit nasty. could use [0] notation to mark this?
141 try {
142 int intIndex = Integer.parseInt(nextToken);
143 nextSelector = new IntIndexSelector(intIndex);
144 } catch (NumberFormatException e) {
145 // OK then so make a StringIndexSelector
146 nextSelector = new StringIndexSelector(nextToken);
147 }
148
149 if (LOG.isDebugEnabled()) {
150 LOG.debug("fromString: nextToken " + nextToken + ": nextSelector " + nextSelector);
151 }
152 if (Debug.ON) {
153 Debug.assertTrue(nextSelector != null, "Couldn't create Selector for: " + nextToken);
154 }
155
156 if (result == null) {
157 result = nextSelector;
158 } else {
159 result.chain(nextSelector);
160 }
161 if (LOG.isDebugEnabled()) {
162 LOG.debug("fromString: result: " + result);
163 }
164 }
165 return result;
166 }
167
168
169 /***
170 * Flatten the Selector list to a String that could be passed into {@link
171 * #fromString} to recreate it.
172 *
173 * @param inSelector flatten this Selector to a String representation.
174 * @return String representation of the passed Selector suitable for passing
175 * back into {@link #fromString} to recreate the Selector list. Return
176 * "" for null Selector.
177 */
178 public static String asString(Selector inSelector) {
179 if (inSelector == null) {
180 return "";
181 }
182
183 StringBuffer result = new StringBuffer();
184 Selector current = inSelector;
185 while (current != null) {
186 result.append(current.getName());
187 result.append(DELIMITER);
188 current = current.getNext();
189 }
190 return result.substring(0, result.length() - DELIMITER.length());
191 }
192
193
194 /***
195 * Get the next Selector in the list, if any. For example, <PRE>
196 * Selector petNameSelector = Selector.fromString("pet.name");
197 * return petNameSelector.getNext();
198 * </PRE> returns a Selector that is <CODE>equals()</CODE> the following
199 * Selector: <PRE>
200 * Selector.fromString("name");
201 * </PRE>
202 *
203 * @return The next value
204 */
205 public final Selector getNext() {
206 return next;
207 }
208
209
210 /***
211 * Get the last Selector in the list. For example, <PRE>
212 * Selector petNameSelector = Selector.fromString("pets.1.name");
213 * return petNameSelector.getLast();
214 * </PRE> returns a Selector that is <CODE>equals()</CODE> the following
215 * Selector: <PRE>
216 * Selector.fromString("name");
217 * </PRE>
218 *
219 * @return The last value
220 */
221 public final Selector getLast() {
222 Selector result = this;
223 while (result.getNext() != null) {
224 result = result.getNext();
225 }
226
227 if (Debug.ON) {
228 Debug.assertTrue(result != null);
229 }
230 return result;
231 }
232
233
234 /***
235 * Used to serialise Selectors {@link #asString} and for debug by {@link
236 * #toString}.
237 *
238 * @return The name value
239 * @todo public access for test cases -- this is nasty.
240 */
241 public abstract String getName();
242
243
244 /***
245 * <P>
246 *
247 * Add a Selector on the end of this list. </P> <P>
248 *
249 * For example: <PRE>
250 * Selector petSelector = Selector.fromString("pet");
251 * Selector nameSelector = Selector.fromString("name");
252 * petSelector.chain(nameSelector);
253 * return petSelector;
254 * </PRE> returns a Selector that is <CODE>equals()</CODE> this one: <PRE>
255 * Selector.fromString("pet.name");
256 * </PRE> </P>
257 *
258 * @param inSelector The Selector to chain at the end of the current
259 * Selector
260 */
261 public final void chain(Selector inSelector) {
262 if (inSelector == null) {
263 throw new IllegalArgumentException("Can't chain a null Selector.");
264 }
265
266 ((Selector) getLast()).setNext(inSelector);
267 }
268
269
270 /***
271 * Remove the terminal Selector. Throws UnsupportedOperationException if
272 * Selector has no chain.
273 */
274 public final void removeLast() {
275 if (getNext() == null) {
276 throw new UnsupportedOperationException("No terminal Selector to remove");
277 }
278 Selector penultimate = this;
279 while (penultimate.getNext().getNext() != null) {
280 penultimate = penultimate.getNext();
281 }
282 penultimate.setNext(null);
283 }
284
285
286 /***
287 * Remove the terminal Selector. Throws UnsupportedOperationException if
288 * Selector has no chain or does no ends with the passed terminal selector.
289 *
290 * @param terminalSelector TODO: Describe the Parameter
291 */
292 public final void removeLast(Selector terminalSelector) {
293 if (getNext() == null) {
294 throw new UnsupportedOperationException("No terminal Selector to remove");
295 }
296 Selector beforeTerminal = this;
297 while (beforeTerminal.getNext().getNext() != null && !beforeTerminal.getNext().equals(terminalSelector)) {
298 beforeTerminal = beforeTerminal.getNext();
299 }
300 if (!beforeTerminal.getNext().equals(terminalSelector)) {
301 throw new UnsupportedOperationException("Terminal Selector " + terminalSelector + " doesn't end Selector " + this);
302 }
303 beforeTerminal.setNext(null);
304 }
305
306 /***
307 * <P>
308 *
309 * Does this Selector list start with the list passed in? </P> <P>
310 *
311 * For example, this returns <CODE>true</CODE>: <PRE>
312 * Selector petSelector = Selector.fromString("pet");
313 * Selector petNameSelector = Selector.fromString("pet.name");
314 * return (petNameSelector.startsWith(petSelector));
315 * </PRE> but this returns <CODE>false</CODE>: <PRE>
316 * Selector nameSelector = Selector.fromString("name");
317 * Selector petNameSelector = Selector.fromString("pet.name");
318 * return (petNameSelector.startsWith(nameSelector));
319 * </PRE> </P>
320 *
321 * @param inSelector A Selector
322 * @return True if this Selector list start with the list passed in
323 */
324 public final boolean startsWith(Selector inSelector) {
325 Selector matchedTarget = inSelector;
326 Selector matchedThis = this;
327 while (matchedTarget != null) {
328 if (matchedThis == null) {
329 // passed in is longer than this
330 return false;
331 }
332 if (matchedThis.shallowEquals(matchedTarget)) {
333 matchedThis = matchedThis.getNext();
334 matchedTarget = matchedTarget.getNext();
335 } else {
336 // No match
337 return false;
338 }
339 }
340 return true;
341 }
342
343
344 /***
345 * <P>
346 *
347 * A deep compare, following down the list of selectors. </P> <P>
348 *
349 * For example, this returns <CODE>true</CODE>: <PRE>
350 * Selector petSelector = Selector.fromString("pet");
351 * Selector nameSelector = Selector.fromString("name");
352 * Selector petNameSelector = Selector.fromString("pet.name");
353 * petSelector.chain(nameSelector);
354 * return (petSelector.equals(petNameSelector));
355 * </PRE> but this returns <CODE>false</CODE>: <PRE>
356 * Selector petSelector = Selector.fromString("pet");
357 * Selector petNameSelector = Selector.fromString("pet.name");
358 * return (petSelector.equals(petNameSelector));
359 * </PRE> </P>
360 *
361 * @param inObject An Object to test for equality, mostly another Selector
362 * @return true if this Selector and the passed object are equal, including
363 * the chained Selectors in the list.
364 */
365 public final boolean equals(Object inObject) {
366
367 // Trivial case: same object
368 if (inObject == this) {
369 return true;
370 }
371
372 // null or wrong class
373 if (!(inObject instanceof Selector)) {
374 return false;
375 }
376
377 // try the shallow equals
378 Selector inSelector = (Selector) inObject;
379 if (!shallowEquals(inSelector)) {
380 return false;
381 }
382
383 if (getNext() == null) {
384 if (inSelector.getNext() == null) {
385 return true;
386 }
387 return false;
388 }
389
390 // and recurse down the list
391 return getNext().equals(inSelector.getNext());
392 }
393
394
395 /***
396 * Return a clone of the entire list of Selectors from <CODE>this</CODE>.
397 *
398 * @return A complete close of this Selector.
399 * @post result.equals(this)
400 */
401 public final Selector deepClone() {
402 Selector result = getShallowCopy();
403 if (getNext() != null) {
404 result.chain(getNext().deepClone());
405 }
406 return result;
407 }
408
409
410 // ------------------- Debug -----------------------
411
412 /***
413 * For debug.
414 *
415 * @return A string representation of this Selector
416 */
417 public final String toString() {
418 StringBuffer result = new StringBuffer();
419 Package p = getClass().getPackage();
420 if (p == null) {
421 result.append(getClass().getName());
422 } else {
423 result.append(getClass().getName().substring(p.getName().length() + 1));
424 }
425 result.append("(");
426 result.append(Selector.asString(this));
427 result.append(")");
428 return result.toString();
429 }
430
431
432 /***
433 * Return a shallow copy of the head of <CODE>this</CODE>.
434 *
435 * @return The shallowCopy value
436 */
437 protected abstract Selector getShallowCopy();
438
439
440 /***
441 * Set the next Selector in the list.
442 *
443 * @param inSelector Selector to set as the next in the list after <CODE>this</CODE>
444 * @see #chain
445 */
446 protected final void setNext(Selector inSelector) {
447 next = inSelector;
448 }
449
450
451 /***
452 * Compare the head Selector of <CODE>this</CODE> against the head of
453 * another Selector list -- ie a shallow compare operation (not including
454 * the chained selectors).
455 *
456 * @param inSelector The Selector to test
457 * @return true if there is equality between this Selector and the other
458 * Selector, excluding the other chained Selectors.
459 */
460 protected abstract boolean shallowEquals(Selector inSelector);
461 }
462
This page was automatically generated by Maven